Be sure to set variables to NULL after freeing them; doing otherwise can lead to...
[adiumx.git] / Plugins / Purple Service / adiumPurpleEventloop.m
blobc3f7ecdc0bb3d18e288f861a616ea79042f91c64
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "adiumPurpleEventloop.h"
18 #import <AIUtilities/AIApplicationAdditions.h>
19 #include <poll.h>
20 #include <unistd.h>
21 #include <sys/socket.h>
22 #include <sys/select.h>
24 //#define PURPLE_SOCKET_DEBUG
26 static guint                            sourceId = 0;           //The next source key; continuously incrementing
27 static CFRunLoopRef                     purpleRunLoop = nil;
29 static void socketCallback(CFSocketRef s,
30                            CFSocketCallBackType callbackType,
31                            CFDataRef address,
32                            const void *data,
33                            void *infoVoid);
35  * The sources, keyed by integer key id (wrapped in an NSNumber), holding
36  * SourceInfo * objects
37  */
38 static NSMutableDictionary      *sourceInfoDict = nil;
40 /*!
41  * @class Holder for various source/timer information
42  *
43  * This serves as the context info for source and timer callbacks.  We use it just as a
44  * struct (declaring all the class's ivars to be public) but make it an object so we can use
45  * reference counting on it easily.
46  */
47 @interface SourceInfo : NSObject {
49 @public CFSocketRef socket;
50 @public int fd;
51 @public CFRunLoopSourceRef run_loop_source;
52         
53 @public guint timer_tag;
54 @public GSourceFunc timer_function;
55 @public CFRunLoopTimerRef timer;
56 @public gpointer timer_user_data;
57         
58 @public guint read_tag;
59 @public PurpleInputFunction read_ioFunction;
60 @public gpointer read_user_data;
61         
62 @public guint write_tag;
63 @public PurpleInputFunction write_ioFunction;
64 @public gpointer write_user_data;       
66 @end
68 @implementation SourceInfo
69 - (NSString *)description
71         return [NSString stringWithFormat:@"<SourceInfo %p: Socket %p: fd %i; timer_tag %i; read_tag %i; write_tag %i>",
72                         self, socket, fd, timer_tag, read_tag, write_tag];
74 @end
76 static SourceInfo *createSourceInfo(void)
78         SourceInfo *info = [[SourceInfo alloc] init];
80         info->socket = NULL;
81         info->fd = 0;
82         info->run_loop_source = NULL;
84         info->timer_tag = 0;
85         info->timer_function = NULL;
86         info->timer = NULL;
87         info->timer_user_data = NULL;
89         info->write_tag = 0;
90         info->write_ioFunction = NULL;
91         info->write_user_data = NULL;
93         info->read_tag = 0;
94         info->read_ioFunction = NULL;
95         info->read_user_data = NULL;    
96         
97         return info;
100 #pragma mark Remove
103  * @brief Given a SourceInfo struct for a socket which was for reading *and* writing, recreate its socket to be for just one
105  * If the sourceInfo still has a read_tag, the resulting CFSocket will be just for reading.
106  * If the sourceInfo still has a write_tag, the resulting CFSocket will be just for writing.
108  * This is necessary to prevent the now-unneeded condition from triggerring its callback.
109  */
110 void updateSocketForSourceInfo(SourceInfo *sourceInfo)
112         CFSocketRef socket = sourceInfo->socket;
113         
114         if (!socket) return;
116         //Reading
117         if (sourceInfo->read_tag)
118                 CFSocketEnableCallBacks(socket, kCFSocketReadCallBack);
119         else
120                 CFSocketDisableCallBacks(socket, kCFSocketReadCallBack);
122         //Writing
123         if (sourceInfo->write_tag)
124                 CFSocketEnableCallBacks(socket, kCFSocketWriteCallBack);
125         else
126                 CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack);
127         
128         //Re-enable callbacks automatically and, by starting with 0, _don't_ close the socket on invalidate
129         CFOptionFlags flags = 0;
130         
131         if (sourceInfo->read_tag) flags |= kCFSocketAutomaticallyReenableReadCallBack;
132         if (sourceInfo->write_tag) flags |= kCFSocketAutomaticallyReenableWriteCallBack;
133         
134         CFSocketSetSocketFlags(socket, flags);
135         
138 gboolean adium_source_remove(guint tag) {
139     SourceInfo *sourceInfo = (SourceInfo *)[sourceInfoDict objectForKey:[NSNumber numberWithUnsignedInt:tag]];
141     if (sourceInfo) {
142 #ifdef PURPLE_SOCKET_DEBUG
143                 AILog(@"adium_source_remove(): Removing for fd %i [sourceInfo %x]: tag is %i (timer %i, read %i, write %i)",sourceInfo->fd,
144                           sourceInfo, tag, sourceInfo->timer_tag, sourceInfo->read_tag, sourceInfo->write_tag);
145 #endif
146                 if (sourceInfo->timer_tag == tag) {
147                         sourceInfo->timer_tag = 0;
149                 } else if (sourceInfo->read_tag == tag) {
150                         sourceInfo->read_tag = 0;
152                 } else if (sourceInfo->write_tag == tag) {
153                         sourceInfo->write_tag = 0;
155                 }
156                 
157                 if (sourceInfo->timer_tag == 0 && sourceInfo->read_tag == 0 && sourceInfo->write_tag == 0) {
158                         //It's done
159                         if (sourceInfo->timer) { 
160                                 CFRunLoopTimerInvalidate(sourceInfo->timer);
161                                 CFRelease(sourceInfo->timer);
162                                 sourceInfo->timer = NULL;
163                         }
164                         
165                         if (sourceInfo->socket) {
166 #ifdef PURPLE_SOCKET_DEBUG
167                                 AILog(@"adium_source_remove(): Done with a socket %x, so invalidating it",sourceInfo->socket);
168 #endif
169                                 CFSocketInvalidate(sourceInfo->socket);
170                                 CFRelease(sourceInfo->socket);
171                                 sourceInfo->socket = NULL;
172                         }
174                         if (sourceInfo->run_loop_source) {
175                                 CFRelease(sourceInfo->run_loop_source);
176                                 sourceInfo->run_loop_source = NULL;
177                         }
178                 } else {
179                         if ((sourceInfo->timer_tag == 0) && (sourceInfo->timer)) {
180                                 CFRunLoopTimerInvalidate(sourceInfo->timer);
181                                 CFRelease(sourceInfo->timer);
182                                 sourceInfo->timer = NULL;
183                         }
184                         
185                         if (sourceInfo->socket && (sourceInfo->read_tag || sourceInfo->write_tag)) {
186 #ifdef PURPLE_SOCKET_DEBUG
187                                 AILog(@"adium_source_remove(): Calling updateSocketForSourceInfo(%x)",sourceInfo);
188 #endif                          
189                                 updateSocketForSourceInfo(sourceInfo);
190                         }
191                 }
192                 
193                 [sourceInfoDict removeObjectForKey:[NSNumber numberWithUnsignedInt:tag]];
195                 return TRUE;
196         }
197         
198         return FALSE;
201 //Like g_source_remove, return TRUE if successful, FALSE if not
202 gboolean adium_timeout_remove(guint tag) {
203     return (adium_source_remove(tag));
206 #pragma mark Add
208 void callTimerFunc(CFRunLoopTimerRef timer, void *info)
210         SourceInfo *sourceInfo = info;
212         if (![sourceInfoDict objectForKey:[NSNumber numberWithUnsignedInt:sourceInfo->timer_tag]])
213                 NSLog(@"**** WARNING: %@ has already been removed, but we're calling its timer function!", info);
215         if (!sourceInfo->timer_function ||
216                 !sourceInfo->timer_function(sourceInfo->timer_user_data)) {
217         adium_source_remove(sourceInfo->timer_tag);
218         }
221 guint adium_timeout_add(guint interval, GSourceFunc function, gpointer data)
223     SourceInfo *info = createSourceInfo();
224         
225         NSTimeInterval intervalInSec = (NSTimeInterval)interval/1000;
226         
227         CFRunLoopTimerContext runLoopTimerContext = { 0, info, CFRetain, CFRelease, /* CFAllocatorCopyDescriptionCallBack */ NULL };
228         CFRunLoopTimerRef runLoopTimer = CFRunLoopTimerCreate(
229                                                                                                                   NULL, /* default allocator */
230                                                                                                                   (CFAbsoluteTimeGetCurrent() + intervalInSec), /* The time at which the timer should first fire */
231                                                                                                                   intervalInSec, /* firing interval */
232                                                                                                                   0, /* flags, currently ignored */
233                                                                                                                   0, /* order, currently ignored */
234                                                                                                                   callTimerFunc, /* CFRunLoopTimerCallBack callout */
235                                                                                                                   &runLoopTimerContext /* context */
236                                                                                                                   );
237         CFRunLoopAddTimer(purpleRunLoop, runLoopTimer, kCFRunLoopCommonModes);
238         [info release];
240         info->timer_function = function;
241         info->timer = runLoopTimer;
242         info->timer_user_data = data;   
243         info->timer_tag = ++sourceId;
245         [sourceInfoDict setObject:info
246                                            forKey:[NSNumber numberWithUnsignedInt:info->timer_tag]];
248         return info->timer_tag;
251 guint adium_input_add(int fd, PurpleInputCondition condition,
252                                           PurpleInputFunction func, gpointer user_data)
253 {       
254         if (fd < 0) {
255                 NSLog(@"INVALID: fd was %i; returning tag %i",fd,sourceId+1);
256                 return ++sourceId;
257         }
259     SourceInfo *info = createSourceInfo();
260         
261     // And likewise the entire CFSocket
262     CFSocketContext context = { 0, info, CFRetain, CFRelease, /* CFAllocatorCopyDescriptionCallBack */ NULL };
264         /*
265          * From CFSocketCreateWithNative:
266          * If a socket already exists on this fd, CFSocketCreateWithNative() will return that existing socket, and the other parameters
267          * will be ignored.
268          */
269 #ifdef PURPLE_SOCKET_DEBUG
270         AILog(@"adium_input_add(): Adding input %i on fd %i", condition, fd);
271 #endif
272         CFSocketRef socket = CFSocketCreateWithNative(NULL,
273                                                                                                   fd,
274                                                                                                   (kCFSocketReadCallBack | kCFSocketWriteCallBack),
275                                                                                                   socketCallback,
276                                                                                                   &context);
278         /* If we did not create a *new* socket, it is because there is already one for this fd in the run loop.
279          * See the CFSocketCreateWithNative() documentation), add it to the run loop.
280          * In that case, the socket's info was not updated.
281          */
282         CFSocketContext actualSocketContext = { 0, NULL, NULL, NULL, NULL };
283         CFSocketGetContext(socket, &actualSocketContext);
284         if (actualSocketContext.info != info) {
285                 [info release];
286                 CFRelease(socket);
287                 info = [(SourceInfo *)(actualSocketContext.info) retain];
288         }
290         info->fd = fd;
291         info->socket = socket;
293     if ((condition & PURPLE_INPUT_READ)) {
294                 info->read_tag = ++sourceId;
295                 info->read_ioFunction = func;
296                 info->read_user_data = user_data;
297                 
298                 [sourceInfoDict setObject:info
299                                                    forKey:[NSNumber numberWithUnsignedInt:info->read_tag]];
300                 
301         } else {
302                 info->write_tag = ++sourceId;
303                 info->write_ioFunction = func;
304                 info->write_user_data = user_data;
305                 
306                 [sourceInfoDict setObject:info
307                                                    forKey:[NSNumber numberWithUnsignedInt:info->write_tag]];            
308         }
309         
310         updateSocketForSourceInfo(info);
311         
312         //Add it to our run loop
313         if (!(info->run_loop_source)) {
314                 info->run_loop_source = CFSocketCreateRunLoopSource(NULL, socket, 0);
315                 if (info->run_loop_source) {
316                         CFRunLoopAddSource(purpleRunLoop, info->run_loop_source, kCFRunLoopCommonModes);
317                 } else {
318                         AILog(@"*** Unable to create run loop source for %p",socket);
319                 }               
320         }
322         [info release];
324     return sourceId;
327 #pragma mark Socket Callback
328 static void socketCallback(CFSocketRef s,
329                                                    CFSocketCallBackType callbackType,
330                                                    CFDataRef address,
331                                                    const void *data,
332                                                    void *infoVoid)
334     SourceInfo *sourceInfo = (SourceInfo *)infoVoid;
335         gpointer user_data;
336     PurpleInputCondition c;
337         PurpleInputFunction ioFunction = NULL;
338         gint     fd = sourceInfo->fd;
340     if ((callbackType & kCFSocketReadCallBack)) {
341                 if (sourceInfo->read_tag) {
342                         user_data = sourceInfo->read_user_data;
343                         c = PURPLE_INPUT_READ;
344                         ioFunction = sourceInfo->read_ioFunction;
345                 } else {
346                         AILog(@"Called read with no read_tag (read_tag %i write_tag %i) for %x",
347                                   sourceInfo->read_tag, sourceInfo->write_tag, sourceInfo->socket);
348                 }
350         } else /* if ((callbackType & kCFSocketWriteCallBack)) */ {
351                 if (sourceInfo->write_tag) {
352                         user_data = sourceInfo->write_user_data;
353                         c = PURPLE_INPUT_WRITE; 
354                         ioFunction = sourceInfo->write_ioFunction;
355                 } else {
356                         AILog(@"Called write with no write_tag (read_tag %i write_tag %i) for %x",
357                                   sourceInfo->read_tag, sourceInfo->write_tag, sourceInfo->socket);
358                 }
359         }
361         if (ioFunction) {
362 #ifdef PURPLE_SOCKET_DEBUG
363                 AILog(@"socketCallback(): Calling the ioFunction for %x, callback type %i (%s: tag is %i)",s,callbackType,
364                           ((callbackType & kCFSocketReadCallBack) ? "reading" : "writing"),
365                           ((callbackType & kCFSocketReadCallBack) ? sourceInfo->read_tag : sourceInfo->write_tag));
366 #endif
367                 ioFunction(user_data, fd, c);
368         }
371 int adium_input_get_error(int fd, int *error)
373         int               ret;
374         socklen_t len;
375         len = sizeof(*error);
376         
377         ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, error, &len);
378         if (!ret && !(*error)) {
379                 /*
380                  * Taken from Fire's FaimP2PConnection.m:
381                  * The job of this function is to detect if the connection failed or not
382                  * There has to be a better way to do this
383                  *
384                  * Any socket that fails to connect will select for reading and writing
385                  * and all reads and writes will fail
386                  * Any listening socket will select for reading, and any read will fail
387                  * So, select for writing, if you can write, and the write fails, not connected
388                  */
389                 
390                 {
391                         fd_set thisfd;
392                         struct timeval timeout;
393                         
394                         FD_ZERO(&thisfd);
395                         FD_SET(fd, &thisfd);
396                         timeout.tv_sec = 0;
397                         timeout.tv_usec = 0;
398                         select(fd+1, NULL, &thisfd, NULL, &timeout);
399                         if(FD_ISSET(fd, &thisfd)){
400                                 ssize_t length = 0;
401                                 char buffer[4] = {0, 0, 0, 0};
402                                 
403                                 length = write(fd, buffer, length);
404                                 if(length == -1)
405                                 {
406                                         /* Not connected */
407                                         ret = -1;
408                                         *error = ENOTCONN;
409                                         AILog(@"adium_input_get_error(%i): Socket is NOT valid", fd);
410                                 }
411                         }
412                 }
413         }
415         return ret;
418 static PurpleEventLoopUiOps adiumEventLoopUiOps = {
419     adium_timeout_add,
420     adium_timeout_remove,
421     adium_input_add,
422     adium_source_remove,
423         adium_input_get_error,
424         /* timeout_add_seconds */ NULL
427 PurpleEventLoopUiOps *adium_purple_eventloop_get_ui_ops(void)
429         if (!sourceInfoDict) sourceInfoDict = [[NSMutableDictionary alloc] init];
431         //Determine our run loop
432         purpleRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
433         CFRetain(purpleRunLoop);
435         return &adiumEventLoopUiOps;